package com.weng.cloud.service8881.concurrenct.algorithm.recursion.resolver;

import com.weng.cloud.service8881.concurrenct.algorithm.recursion.base.Node;
import com.weng.cloud.service8881.concurrenct.algorithm.recursion.base.Puzzle;
import com.weng.cloud.service8881.concurrenct.algorithm.recursion.base.PuzzleSolver;
import com.weng.cloud.service8881.concurrenct.algorithm.recursion.component.ValueLatch;
import lombok.Setter;

import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;

/**
 * @Author: weng
 * @Date: 2022/7/9
 * @Description: com.weng.cloud.service8881.concurrenct.algorithm.recursion.resolver
 */

public class ConcurrentPuzzleSolver<P, M> implements PuzzleSolver<P, M>{
    @Setter
    private Puzzle<P, M> puzzle;
    private final ExecutorService executorService;
    private final ConcurrentMap<P, Boolean> seen;
    protected final ValueLatch<Node<P, M>> solution = new ValueLatch<>();

    public ConcurrentPuzzleSolver(ExecutorService executorService, ConcurrentMap<P, Boolean> seen) {
        this.executorService = executorService;
        this.seen = seen;
    }

    @Override
    public List<M> solve() throws InterruptedException {
        try {
            P position = puzzle.initialPosition();
            executorService.execute(newTask(position, null, null));
            Node<P, M> ans = solution.getValue();
            return ans == null ? null : ans.asMoveList();
        } finally {
            executorService.shutdown();
        }
    }

    protected Runnable newTask(P position, M move, Node<P, M> node) {
        return new SolverTask(position, move, node);
    }

    class SolverTask extends Node<P, M> implements Runnable {
        public SolverTask(P position, M move, Node<P, M> prev) {
            super(position, move, prev);
        }

        @Override
        public void run() {
            if (solution.isSet() || seen.putIfAbsent(getPosition(), true) != null) {
                return;
            }
            if (puzzle.isGoal(getPosition())) {
                solution.setValue(this);
                return;
            }
            for (M move : puzzle.legalMoves(getPosition())) {
                executorService.execute(newTask(puzzle.move(getPosition(), move), move, this));
            }
        }
    }
}
